/*
    Contact info:
    bhepple@freeshell.org
    http://bhepple.freeshell.org/scrmgr

    Copyright (C) 1991-2008 Bob Hepple

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; see the file COPYING.  If not, write to
    the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
    Boston, MA 02111-1307, USA.

*/
/* smlib.c ... all of the nasty details of managing SM_SCREEN's SM_FIELD's
   etc etc */
#include <string.h>
#include <time.h>
#include <setjmp.h>

#define SM_CLASS extern    
#include "smlib.h"
#include "smio.h"

char *smlib_rcsid(void) {
    return("$Id: smlib.c,v 1.7 2008/02/19 13:15:50 bhepple Exp $");
}

			/* Initialise the naughty globals */

CHAR 			sm_version[] 	= "$Revision: 1.7 $";
CHAR 			sm_msg[100] 	= "";
CHAR			sm_program_name[MAXNAMELEN + 1];
CHAR			sm_program_directory[MAXPATHLEN + 1];

CHAR			sm_mem_error[] = "Ran out of RAM! (heap)";

/* Root of the data heirarchy ... */
struct sm_file_s sm_f_file[SM_NUM_STRUCTURES];

CHAR			sm_default_separator = '~';

static ARRAY		sm_user_keyword = NULL; /* For the user's commands */
ARRAY	 		sm_keyword = NULL;	 /* For SCRMGR file values */
ARRAY			sm_command_table = NULL;/* For SCRMGR commands */
static SM_MENU		sm_current_menu = NULL;
int			sm_use_menus = 0;

SM_FILE sm_user_screens()
{
    return(&sm_f_file[SM_USER_SCREENS]);
}

SM_FILE sm_extra_screens()
{
    return(&sm_f_file[SM_EXTRA_SCREENS]);
}

SM_FILE sm_builtin_screens()
{
    return(&sm_f_file[SM_BUILTIN_SCREENS]);
}

SM_FILE sm_scrmgr_screens()
{
    return(&sm_f_file[SM_SCRMGR_SCREENS]);
}
    
void sm_pre_init ARG2(int, argc, CHAR **, argv)
{
    CHAR	*p;

    /* if we ran 'smcreate' then sm_program_name would be 'smcreate'
       and sm_directory_name would be A:/path/to/bin/
       note the terminal /  */
    fname(sm_program_name, argv[0]);
    fqn(sm_program_directory, argv[0]);
#if BACKSLASH_AND_SLASH
    if ((p = strrchr(sm_program_directory, '\\')) != NULL)
	*++p = 0;
    else
#endif 
	if ((p = strrchr(sm_program_directory, '/')) != NULL)
	    *++p = 0;
}

void sm_init ARG3(int, interactive, int, argc, CHAR **, argv)
{
    int 	i;

#define SET_KEYWORD(a, b) sm_keyword=sm_set_keyword(sm_keyword,a,b,NULL)
    
    /* Here are the fields that SCRMGR recognises in a screen data .dat file */
    SET_KEYWORD(SM_NAME_F,		"NAME");
    SET_KEYWORD(SM_SCROLLING_F,		"SCROLLING");
    SET_KEYWORD(SM_ROW_F,		"ROW");
    SET_KEYWORD(SM_COL_F,		"COL");
    SET_KEYWORD(SM_ATTRIBUTE_F,		"ATTRIBUTE");
    SET_KEYWORD(SM_HIDDEN_F,		"HIDDEN");
    SET_KEYWORD(SM_PROTECT_F,		"PROTECT");
    SET_KEYWORD(SM_FTYPE_F,		"FTYPE");
    SET_KEYWORD(SM_LEN_F,		"LEN");
    SET_KEYWORD(SM_MAXLEN_F,		"MAXLEN");
    SET_KEYWORD(SM_C_DEFAULT_F,		"C_DEFAULT");
    SET_KEYWORD(SM_KEYSCREEN_F,		"KEY_SCREEN");
    SET_KEYWORD(SM_TRIGGER_FN_F,	"TRIGGER_FN");
    SET_KEYWORD(SM_TEXT_F,		"TEXT");
    SET_KEYWORD(SM_UPPER_F,		"UPPER");
    SET_KEYWORD(SM_LOWER_F,		"LOWER");
    SET_KEYWORD(SM_KEY_NAME_F,		"KEY_NAME");
    SET_KEYWORD(SM_TOGGLE_F,		"TOGGLE");
    SET_KEYWORD(SM_KEY_HELP_F,		"KEY_HELP");
    SET_KEYWORD(SM_CALLBACK_FN_F,	"CALLBACK_FN");
    SET_KEYWORD(SM_SCR_TABLE_F,		"SCREEN_TABLE");
    SET_KEYWORD(SM_FLD_TABLE_F,		"FIELD_TABLE");
    SET_KEYWORD(SM_BG_TABLE_F,		"BG_TABLE");
    SET_KEYWORD(SM_SKEY_TABLE_F,	"SKEY_TABLE");
    SET_KEYWORD(SM_FIELD_F,		"FIELD_RECORD");
    SET_KEYWORD(SM_BG_F,		"BG_RECORD");
    SET_KEYWORD(SM_SCREEN_F,		"SCREEN_RECORD");
    SET_KEYWORD(SM_KEY_F,		"KEY_RECORD");
    SET_KEYWORD(SM_SEPARATOR_F,		"SEPARATOR");
    SET_KEYWORD(SM_SCREEN_FIELDNAMES_F,	"SCREEN_FIELDNAMES");
    SET_KEYWORD(SM_FIRST_FIELD_F,	"FIRST_FIELD");
    SET_KEYWORD(SM_NEXT_FIELD_F,	"NEXT_FIELD");
    SET_KEYWORD(SM_FILE_TABLE_F,	"FILE_TABLE");
    SET_KEYWORD(SM_TAG_F,		"TAG");
    SET_KEYWORD(SM_FILE_F,		"FILE_RECORD");
    SET_KEYWORD(SM_SORT_FIELD_F,	"SORT_FIELD");
    SET_KEYWORD(SM_SORT_IGNORE_CASE_F,	"SORT_IGNORE_CASE");
    SET_KEYWORD(SM_SORT_MONTH_F,	"SORT_MONTH");
    SET_KEYWORD(SM_SORT_NUMERIC_F,	"SORT_NUMERIC");
    SET_KEYWORD(SM_SORT_REVERSE_F,	"SORT_REVERSE");
    SET_KEYWORD(SM_CONSTANT_START_F,	"CONSTANT_START");
    SET_KEYWORD(SM_CONSTANT_END_F,	"CONSTANT_END");
    SET_KEYWORD(SM_FILENAME_F,		"FILENAME");
    SET_KEYWORD(SM_TEMP_STORAGE_F,	"TEMP_STORAGE");
    SET_KEYWORD(SM_SCREEN_SOFTKEY_F,	"SCREEN_SOFTKEY");
    SET_KEYWORD(SM_AUTO_ADJUST_F,	"AUTO_ADJUST");
    SET_KEYWORD(SM_MENU_F,		"MENU_RECORD");
    SET_KEYWORD(SM_MENU_TABLE_F,	"MENU_TABLE");
    SET_KEYWORD(SM_MENU_TYPE_F,		"MENU_TYPE");
    SET_KEYWORD(SM_MENU_TEXT_F,		"MENU_TEXT");
    SET_KEYWORD(SM_MENU_COMMAND_F,	"MENU_COMMAND");
    SET_KEYWORD(SM_SORT_KEY_F,		"SORT_KEY"); /* obsolete */
    SET_KEYWORD(SM_ASCENDING_F,		"ASCENDING"); /* obsolete */
    SET_KEYWORD(SM_IGNORE_CASE_F,	"IGNORE_CASE"); /* obsolete */
    
#undef  SET_KEYWORD
#define SET_KEYWORD(a, b, c) \
sm_command_table=sm_set_keyword(sm_command_table,a,b,c)
    
    /* ... and here are the commands that SCRMGR knows about */
    SET_KEYWORD(SM_LEFT_C,		"sm_left_c",		"Left");
    SET_KEYWORD(SM_RIGHT_C,		"sm_right_c",		"Right");
    SET_KEYWORD(SM_TOGGLE_INS_C,       	"sm_toggle_ins_c", 	"Toggle insert");
    SET_KEYWORD(SM_DEL_CHAR_C,		"sm_del_char_c",	"Delete character");
    SET_KEYWORD(SM_DEL_BACK_C,		"sm_del_back_c",	"Backspace");
    SET_KEYWORD(SM_NEXT_FIELD_C,       	"sm_next_field_c",	"Tab");
    SET_KEYWORD(SM_NEXT_LINE_C,		"sm_next_line_c",	"Return");
    SET_KEYWORD(SM_PREV_FIELD_C,       	"sm_prev_field_c",	"Back tab");
    SET_KEYWORD(SM_HOME_FIELD_C,       	"sm_home_field_c", 	"Home");
    SET_KEYWORD(SM_END_FIELD_C,		"sm_end_field_c",	"End");
    SET_KEYWORD(SM_CLEAR_FIELD_C,      	"sm_clear_field_c",	"Clear to end of field");
    SET_KEYWORD(SM_TRANSPOSE_CHARS_C,	"sm_transpose_chars_c",	"Transpose characters");
    SET_KEYWORD(SM_CANCEL_C,		"sm_cancel_c",		"Cancel");

    SET_KEYWORD(SM_TOGGLE_CASE_C,      	"sm_toggle_case_c",	"Toggle case");
    SET_KEYWORD(SM_LEFT_WORD_C,		"sm_left_word_c",	"Word left");
    SET_KEYWORD(SM_RIGHT_WORD_C,       	"sm_right_word_c",	"Word right");
    SET_KEYWORD(SM_DEL_WORD_C,		"sm_del_word_c",	"Delete word");
    SET_KEYWORD(SM_DEL_WORD_BACK_C,    	"sm_del_word_back_c",	"Delete previous word");
    SET_KEYWORD(SM_TRANSPOSE_WORDS_C,	"sm_transpose_words_c", "Transpose words");
    SET_KEYWORD(SM_TELL_VERSION_C,	"sm_tell_version_c",	"Version");
    
    SET_KEYWORD(SM_HOME_SCREEN_C,	"sm_home_screen_c",	"Top of screen");
    SET_KEYWORD(SM_END_SCREEN_C,	"sm_end_screen_c",	"Bottom of screen");
    SET_KEYWORD(SM_CLEAR_SCREEN_C,	"sm_clear_screen_c"	,"Clear to end of screen");
    SET_KEYWORD(SM_NEXT_SCREEN_C,	"sm_next_screen_c",	"Next record");
    SET_KEYWORD(SM_PREV_SCREEN_C,	"sm_prev_screen_c",	"Previous record");
    SET_KEYWORD(SM_FIRST_SCREEN_C,	"sm_first_screen_c",	"First record");
    SET_KEYWORD(SM_LAST_SCREEN_C,	"sm_last_screen_c",	"Last record");
    SET_KEYWORD(SM_SELECT_SCREEN_C,	"sm_select_screen_c",	"Display a specific screen");
    SET_KEYWORD(SM_INSERT_DATA_C,	"sm_insert_data_c",	"Insert a new record");
    SET_KEYWORD(SM_DELETE_DATA_C,	"sm_delete_data_c",	"Delete record");
    SET_KEYWORD(SM_SPLIT_LINE_C,	"sm_split_line_c",	"Split line");
    SET_KEYWORD(SM_JOIN_LINES_C,	"sm_join_lines_c",	"Join lines");
    SET_KEYWORD(SM_YANK_DATA_C,		"sm_yank_data_c",	"Copy record from 'YANK' buffer");
    SET_KEYWORD(SM_COPY_DATA_C,		"sm_copy_data_c",	"Copy record to 'YANK' buffer");
    SET_KEYWORD(SM_COPY_FIELD_C,       	"sm_copy_field_c",	"Copy field to 'YANK' buffer");
    SET_KEYWORD(SM_YANK_FIELD_C,	"sm_yank_field_c",	"Copy field from 'YANK' buffer");
    SET_KEYWORD(SM_DELETE_FIELD_C,	"sm_delete_field_c",	"Delete field");
    SET_KEYWORD(SM_CHANGE_TYPE_C,	"sm_change_type_c",	"Change type");
    
    SET_KEYWORD(SM_SHELL_OUT_C,		"sm_shell_out_c",	"Shell out");
    SET_KEYWORD(SM_QUIT_C,	      	"sm_quit_c", 		"Quit");
    SET_KEYWORD(SM_REFRESH_C,		"sm_refresh_c",		"Refresh screen");
    SET_KEYWORD(SM_UP_C,	       	"sm_up_c",		"Up");
    SET_KEYWORD(SM_DOWN_C,	       	"sm_down_c",		"Down");
    SET_KEYWORD(SM_EDIT_MASTER_C,   	"sm_edit_master_c",	"Edit master file");
    SET_KEYWORD(SM_POPUP_C,  		"sm_popup_c",		"Popup values");
    SET_KEYWORD(SM_HELP_C,		"sm_help_c",		"Help system");
    SET_KEYWORD(SM_KEY_HELP_C,		"sm_key_help_c",	"Key help");
    SET_KEYWORD(SM_KEY_LISTS_C,		"sm_key_lists_c",	"List the key assignments");
    SET_KEYWORD(SM_CALLBACK_FN_C,	"sm_callback_fn_c",	"Callback function");
    SET_KEYWORD(SM_LOAD_FILE_C,		"sm_load_file_c", 	"Load file");
    SET_KEYWORD(SM_STORE_FILE_C,	"sm_store_file_c", 	"Store file");
    SET_KEYWORD(SM_STORE_FILE_AS_C,	"sm_store_file_as_c", 	"Store file as ...");
    SET_KEYWORD(SM_CLEAR_FILE_C,	"sm_clear_file_c", 	"Clear file");
    SET_KEYWORD(SM_EXIT_C,		"sm_exit_c",		"Exit program");
    SET_KEYWORD(SM_NO_SAVE_EXIT_C,	"sm_no_save_exit_c",	"Exit program without saving files");
    SET_KEYWORD(SM_YES_C,		"sm_yes_c",		"Yes");
    SET_KEYWORD(SM_NO_C,		"sm_no_c",		"No");
    
    SET_KEYWORD(SM_GOTO_RECORD_C,	"sm_goto_record_c",	"Go to record #");
    SET_KEYWORD(SM_IGNORE_CASE_C,	"sm_ignore_case_c",	"Ignore case");
    SET_KEYWORD(SM_SEARCH_REP_C,	"sm_search_rep_c",	"Search & replace");
    SET_KEYWORD(SM_SEARCH_C,		"sm_search_c", 		"Search");
    SET_KEYWORD(SM_REPLACE_ALL_C,	"sm_replace_all_c",	"Replace all");
    SET_KEYWORD(SM_AGAIN_C,		"sm_again_c",		"Continue search/replace");
    SET_KEYWORD(SM_SEARCH_BACKWARDS_C,	"sm_search_backwards_c","Search backwards");
    SET_KEYWORD(SM_WHERE_C,		"sm_where_c", 		"Where...");
    SET_KEYWORD(SM_NEXT_KEYS_C,		"sm_next_keys_c",	"Next keys");
    SET_KEYWORD(SM_SORT_C,		"sm_sort_c",		"Sort");
    SET_KEYWORD(SM_WHERE_CONFIRM_C,	"sm_where_confirm_c",	"Confirm 'WHERE' operation");
    SET_KEYWORD(SM_WHERE_SKIP_C,	"sm_where_skip_c",	"Skip next 'WHERE' operation");
    SET_KEYWORD(SM_WHERE_DO_ALL_C,	"sm_where_do_all_c",	"Do all remaining 'WHERE' operations");
    SET_KEYWORD(SM_MOUSE_EVENT_C,	"sm_mouse_event_c",	"Mouse button");
    SET_KEYWORD(SM_HELP_MANUAL_C,	"sm_help_manual_c",	"Display manual");
    SET_KEYWORD(SM_SCRMGR_MANUAL_C,	"sm_scrmgr_manual_c",	"Display SCRMGR manual");
    SET_KEYWORD(SM_EXEC_PROG_C,		"sm_exec_prog_c",	"Run a command");
    SET_KEYWORD(SM_EXEC_PROG_NO_ERR_C,	"sm_exec_prog_no_err_c","Run a command without watching stderr");
    SET_KEYWORD(SM_POPUP_MENU_C,	"sm_popup_menu_c",	"Popup a new menu");
    
    /* Create base structures */
    for (i = 0; i < SM_NUM_STRUCTURES; i++) {
	sm_f_file[i].skey =	newArray(10, 10);
	sm_f_file[i].menu =	newArray(10, 10);
	sm_f_file[i].scr =	newArray(10, 10);
	
	sm_f_file[i].tagname = NULL;
	sm_f_file[i].separator = sm_default_separator;
    }
    
    smio_init(interactive, argc, argv);

    if (interactive)
	sm_read_built_ins();
}

void sm_enq_term ARG3(int, window, int *, row, int *, col) 
{
    smio_enq(window, row, col);
}

int sm_enq_mouse ARG3(int *, row, int *, col, int *, delta)
{
    return(smio_enq_mouse(row, col, delta));
}

int sm_anything_there ARG1(SM_FILE, screenset)
{
    if (sm_first_scr(screenset)  || 
	sm_first_skey(screenset) || 
	sm_first_menu(screenset))
	return(TRUE);
    else
	return(FALSE);
}

void _sm_clear_out ARG1(int, i)
{
    sm_destroy_all_scrs(&sm_f_file[i]);
    sm_destroy_all_skeys(&sm_f_file[i]);
    sm_destroy_all_menus(&sm_f_file[i]);

    if (sm_f_file[i].tagname)
	free(sm_f_file[i].tagname);
    sm_f_file[i].tagname = NULL;

    sm_f_file[i].separator = sm_default_separator;
}

static void _sm_destroy_keyword ARG1(SM_KEYWORD, k)
{
    assert(k);

    if (k->string)
	free(k->string);
    if (k->help)
	free(k->help);
    free(k);
}

SM_KEYWORD *sm_destroy_keyword ARG1(SM_KEYWORD *, kp)
{
    assert(kp && *kp);

    _sm_destroy_keyword(*kp);
    return((SM_KEYWORD *) removeFromArrayp((*kp)->parent, (void **) kp));
}

void sm_destroy_all_keywords ARG1(ARRAY, table)
{
    assert(table);

    removeAllItemsFromArray(table, (f_ptr) _sm_destroy_keyword);
}

/* Clear out all screens and softkeys (except prompts etc) */
void sm_clear_out ARG1(int, clear_all)
{
    int		i;

    if (clear_all) {
	for (i = 1; i < SM_NUM_STRUCTURES; i++) {
	    _sm_clear_out(i);
	    freeArray(sm_f_file[i].scr);
	    freeArray(sm_f_file[i].skey);
	    freeArray(sm_f_file[i].menu);
	}

	if (sm_user_keyword) {
	    sm_destroy_all_keywords(sm_user_keyword);
	    freeArray(sm_user_keyword);
	}
	if (sm_command_table) {
	    sm_destroy_all_keywords(sm_command_table);
	    freeArray(sm_command_table);
	}
	if (sm_keyword) {
	    sm_destroy_all_keywords(sm_keyword);
	    freeArray(sm_keyword);
	}
    } else {
	_sm_clear_out(SM_USER_SCREENS); /* user screens only */
	_sm_clear_out(SM_EXTRA_SCREENS); /* extra screens only */
    }
}

/* Get ready to retire */
void sm_term()
{
    sm_clear_out(1);
    smio_term();
}

int sm_destroy_callback()
{
    return(smio_destroy_callback());
}

void sm_push_command ARG1(int, command)
{
    smio_push_command(command);
}

void sm_add_callback ARG2(f_ptr, lambda, void *, object)
{
    smio_add_callback(lambda, object);
}

void sm_main_loop()
{
    smio_main_loop();
}

/* Let user maintain a keyword<->command look-up */
int sm_set_command ARG3(int, 		command, 
			CHAR *, 	string,
			CHAR *,		help)
{
    /* See if it's already there in the command_table or in the
       user table - reject it if it is */
    if (sm_lookup_command(string) != SM_ERROR)
	return(SM_ERROR);

    sm_user_keyword = sm_set_keyword(sm_user_keyword, command, string, help);
    return(0);
}

ARRAY sm_set_keyword ARG4(ARRAY,	table, 
			  int, 		command, 
			  CHAR *, 	string, 
			  CHAR *, 	help)
{
    SM_KEYWORD	t;

    assert(string && *string);

    t = (SM_KEYWORD) xmalloc((INDEX) sizeof(struct sm_keyword_s));
    t->string = (CHAR *) xmalloc((INDEX) (strlen(string) + 1));
    strcpy(t->string, string);
    t->value = command;
    if (help) {
	t->help = (CHAR *) xmalloc((INDEX) (strlen(help) + 1));
	strcpy(t->help, help);
    } else
	t->help = NULL;
    t->argument = NULL;
    table = addToArray(table, t);
    t->parent = table;
    return(table);
}

/* INT->string e.g. command to string, key to string etc */
CHAR *sm_lookup_string ARG2(ARRAY, table, int, value)
{
    SM_KEYWORD	*keyword;

    assert(table);
    
    for (keyword = (SM_KEYWORD *) firstArrayItemp(table);
	 keyword && *keyword;
	 keyword = (SM_KEYWORD *) nextArrayItemp(table, (void *) keyword))
	if (value == (*keyword)->value)
	    return((*keyword)->string);
    return(NULL);
}

/* INT->help_string e.g. command to help_string */
CHAR *sm_lookup_help ARG2(ARRAY, table, int, value)
{
    SM_KEYWORD	*k;

    assert(table);
    
    for (k = (SM_KEYWORD *) firstArrayItemp(table);
	 k && *k;
	 k = (SM_KEYWORD *) nextArrayItemp(table, (void *) k))
	 if ((*k)->value == value)
	     return((*k)->help);
    return(NULL);
}

/* key word look up function - e.g. string to command */
int sm_lookup_keyword ARG2(ARRAY, table, CHAR *, line)
{
    SM_KEYWORD *k;

    assert(table && line);
    
    for (k = (SM_KEYWORD *) firstArrayItemp(table);
	 k && *k;
	 k = (SM_KEYWORD *) nextArrayItemp(table, (void *) k))
	 if (strcmp((*k)->string, line) == 0)
	     return((*k)->value);
    return(SM_ERROR);
}

int sm_lookup_command ARG1(CHAR *, string)
{
    int		command = 0;

    command = sm_lookup_keyword(sm_command_table, string);
    if (command != SM_ERROR)
	return(command);

    if (sm_user_keyword == NULL)
	return(SM_ERROR);
    return(sm_lookup_keyword(sm_user_keyword, string));
}

void sm_line_error ARG3(CHAR *, msg, CHAR *, offense, int, line_num)
{
    smio_line_error(msg, offense, line_num);
}

void sm_error ARG1(CHAR *, msg)
{
    smio_error(msg);
}

void sm_file_error ARG3(CHAR *, msg, CHAR *, filename, int, error)
{
    CHAR	*buf = alloca(MAXPATHLEN);

    sprintf(buf, "%s\"%s\": %s", msg, filename,
#if !NO_SYS_NERR
	    error >= sys_nerr? "weird error":
#endif
        strerror(error));
    sm_error(buf);
}

void sm_message ARG1(CHAR *, msg)
{
    smio_message(msg);
    strcpy(sm_msg, msg);
}

void sm_bell()
{
    smio_bell();
}


/* Increase the size of a malloc'd array of pointers by one. End of
   array is marked by NULL. Successful call returns the array and the
   offset of the next available element, else NULL.
   
   Important note. Since this uses 'realloc' the location of 'array' may
   be changed after this call. Therefore, take care with pointers into 
   'array'. For example, suppose 'current_pos' points into 'array'. Then,
   to avoid trouble:
   
   i = current_pos - array;
   array = sm_increase_size(array, &size);
   current_pos = &array[i]; ... re-establish pointer
   
   In case you have not thought about it before (I hadn't!), this is true 
   for realloc and any routine that uses it. Dangerous!
   
   In this version, the first 'chunksize' allocs are done one at a time
   and then in chunks of 'chunksize'.

   */

SM_DATA sm_increase_size ARG2(SM_DATA, array, INDEX *, count)
{
    INDEX	last_element,
    		num_chunks,
    		chunksize = 100,
    		size_to_alloc;
    SM_DATA	a;
    
    if (chunksize < (INDEX) 100)
	chunksize = 100;

    if (array == NULL) {
	array = (SM_DATA) xmalloc((INDEX) (sizeof(CHAR *) * 2));
	array[0] = NULL;
	array[1] = NULL;
	*count = 0;
	return(array);
    }
    
    /* Find end and size of array */
    if (*count == MAX_INDEX)
	*count = 0;

    a = &array[*count];
    while (*a) {
	(*count)++;
	a++;
    }
    
    last_element = *count + 1; /* allowing for end marker */
    size_to_alloc = 0;

    if (last_element < chunksize)
	size_to_alloc = (last_element + 1) * sizeof(CHAR *);
    else if ((last_element % chunksize) == (INDEX) 0) {
	num_chunks = last_element / chunksize + 1;
	size_to_alloc = num_chunks * chunksize * sizeof(CHAR *);
    }
    if (size_to_alloc)
 	array = (SM_DATA) xrealloc((CHAR *) array, (INDEX) size_to_alloc);

    array[*count + 1] = NULL;
    return(array);
}

void sm_delete_data ARG1(SM_DATA, data)
{
    SM_DATA temp;
    
    if (data) {
	temp = data;
	while (*temp) {
	    free(*temp);
	    temp++;
	}
	free(data);
    }
}

/* Find the next field in 'line'. Use 'separator' to delimit the field.
 * If this field is 'fixed length' (max_length > 0) then return NULL. 
 * (Calling routine should use
 * strnchr(dest, line, max_length) to get the field). Otherwise, set the
 * separator position to 0 to delimit the string.
 */

CHAR *sm_parse ARG3(CHAR *, line, int, max_length, CHAR, separator)
{
    CHAR	*sep;
    
    if (max_length)
	return(NULL);

    if (separator == ' ')
	sep = strpbrk(line, " \t");
    else
	sep = strchr(line, separator);
    if (sep == NULL)
	sep = strchr(line, 0);
    if (sep)
	*sep = 0;
    return(sep);
}

/* trim left and right whitespace from 'line' */
CHAR *sm_trim ARG1(CHAR *, line)
{
    CHAR	*end;
    
    while (*line && isspace(*line))
	line++;
    
    end = SM_LASTCHAR(line);
    while ((end > line) && isspace(*end))
	*end-- = 0;
    return(line);
}

/* point to next field */
CHAR *sm_nextfield ARG4(int, 	separator,
			CHAR *, line, 
			CHAR *, sep, 
			int, 	max_length)
{
    if (sep) {
	line = sep + 1;
	if (separator == ' ')
	    while ((*line == ' ') || (*line == '\t'))
		line++;
    } else if (max_length)
	line += max_length;
    else
	line = strchr(line, 0);
    return(line);
}

void sm_toupper ARG1(CHAR *, p)
{
    while (*p) {
	if (islower(*p))
	    *p = toupper(*p);
	p++;
    }
}

void sm_tolower ARG1(CHAR *, p)
{
    while (*p) {
	if (isupper(*p))
	    *p = tolower(*p);
	p++;
    }
}

/* Convert a general name to something that 'c' will digest (replaces
   funny characters with _ */

int sm_fix_name ARG1(CHAR *, p)
{
    int not_first = FALSE;
    int retval = 0;
    
    while (*p) {
	if (isalpha(*p) || (*p == '_') || (not_first && isdigit(*p)))
	    p++;
	else {
	    *p++ = '_';
	    retval = 1;
	}
	not_first = TRUE;
    }
    return(retval);
}

/* Safely copy 'from' to 'to'. 'to' is a malloc'd string and can be
 * expanded with realloc - that is the value we return.
 */

CHAR *sm_assign ARG3(CHAR *, to, CHAR *, from, int, max_length)
{
    CHAR	blank[1];
    int		length;
    
    if (from == NULL) {
	blank[0] = 0;
	from = &blank[0];
    }
    
    if (max_length)
	length = max_length;
    else
	length = strlen(from);

    length++;
    
    to = xrealloc(to, (INDEX) length);
    
    if (max_length) {
	strncpy(to, from, max_length);
	while (strlen(to) < max_length)
	    strcat(to, " ");
	to[max_length] = 0;
    } else
	strcpy(to, from);
    
    return(to);
}

extern jmp_buf		NoMoreHeap;
CHAR *xmalloc ARG1(INDEX, size)
{
    register CHAR 	*retval;

    assert((size > 0) && (size < MAX_INDEX));

    if ((retval = malloc((size_t) size)))
	return(retval);
    /* Problem - no more heap */
    longjmp(NoMoreHeap, 1);
    return(NULL);
}

CHAR *xrealloc ARG2(CHAR *, oldbuf, INDEX, size)
{
    register CHAR 	*retval;

    assert((size > 0) && (size < MAX_INDEX));

    if (oldbuf) {
	if ((retval = realloc(oldbuf, (size_t) size)))
	    return(retval);
    } else
	return(xmalloc(size));

    /* Problem - no more heap */
    longjmp(NoMoreHeap, 1);
    return(NULL);
}

/*
 *
 * Softkey label control and enquiry
 *
 */

SM_SOFTKEY sm_create_skey ARG1(SM_FILE,	screenset)
{
    SM_SOFTKEY	softkey_set;
    int		i;
    
    assert(screenset);

    softkey_set = (SM_SOFTKEY) xmalloc((INDEX) sizeof(struct sm_skey));
    softkey_set->skey_name = NULL;
    softkey_set->parent = screenset;

    for (i = 0; i < 8; i++) {
	softkey_set->upper[i] = xmalloc((INDEX) 9);
	strcpy(softkey_set->upper[i], "	       ");
	
	softkey_set->lower[i] = xmalloc((INDEX) 9);
	strcpy(softkey_set->lower[i], "	       ");
	softkey_set->key_name[i] = NULL;
	softkey_set->toggle[i] = SM_NOT_TOGGLE;
	softkey_set->key_help[i] = NULL;
	softkey_set->key_argument[i] = NULL;
    }

    screenset->skey = addToArray(screenset->skey, softkey_set);
    return(softkey_set);
}

/* Release memory */
static void _sm_destroy_skey ARG1(SM_SOFTKEY, softkey_set)
{
    int	i;
    
    assert(softkey_set);
    
    if (softkey_set->skey_name)
	free(softkey_set->skey_name);
    
    for (i = 0; i < 8; i++) {
	free(softkey_set->upper[i]);
	free(softkey_set->lower[i]);
	if (softkey_set->key_name[i])
	    free(softkey_set->key_name[i]);
	if (softkey_set->key_help[i])
	    free(softkey_set->key_help[i]);
	if (softkey_set->key_argument[i])
	    free(softkey_set->key_argument[i]);
    }
    free(softkey_set);
}

SM_SOFTKEY *sm_destroy_skey ARG1(SM_SOFTKEY *, softkey_set)
{
    assert(softkey_set && *softkey_set);

    _sm_destroy_skey(*softkey_set);
    return((SM_SOFTKEY *) removeFromArrayp((*softkey_set)->parent->skey, 
					   (void **) softkey_set));
}

void sm_destroy_all_skeys ARG1(SM_FILE,	screenset)
{
    assert(screenset);

    if (screenset->skey)
	removeAllItemsFromArray(screenset->skey, (f_ptr) _sm_destroy_skey);
}

SM_SOFTKEY sm_skey_open ARG2(SM_FILE, screenset, CHAR *, softkey_name) 
{
    SM_SOFTKEY	*softkey_set;
    
    softkey_set = sm_first_skey(screenset);
    while (softkey_set && *softkey_set && 
	   strcmp((*softkey_set)->skey_name, softkey_name))
	softkey_set = sm_next_skey(softkey_set);
    return(softkey_set? *softkey_set: NULL);
}

void sm_skey_init ARG1(SM_SOFTKEY, softkey_set) 
{
}

void sm_paint_skey ARG1(SM_SOFTKEY, softkey_set) 
{
    smio_paint_skey(softkey_set);
}

/* Display no key labels */
void sm_null_skey()
{
    smio_null_skey();
}

CHAR *sm_skey_name ARG2(SM_SOFTKEY, softkey_set, int, keynum)
{
    assert((keynum >= 0) && (keynum <= 7));
    
    return(softkey_set->key_name[keynum]);
}

int sm_enq_skey ARG2(SM_SOFTKEY, softkey_set, CHAR *, key_name) 
{
    int	i;
    
    for (i = 0; i < 8; i++)
	if (strcmp(key_name, softkey_set->key_name[i]) == 0)
	    return(softkey_set->toggle[i]);
    return(SM_NOT_TOGGLE);
}

void sm_set_skey ARG3(SM_SOFTKEY, softkey_set, CHAR *, key_name, int, value) 
{
    int	i;

    assert(softkey_set);

    for (i = 0; i < 8; i++)
	if (strcmp(key_name, softkey_set->key_name[i]) == 0) {
	    softkey_set->toggle[i] = value;
	    return;
	}
}

void sm_set_skey_label ARG4(SM_SOFTKEY, skey,
			    int,	key_num,
			    CHAR *,	upper,
			    CHAR *,	lower)
{
    assert(skey);
    assert(key_num >= 0);
    assert(key_num <= 8);

    SM_FASSIGN(skey->upper[key_num], upper, 8);
    SM_FASSIGN(skey->lower[key_num], lower, 8);
    return;
}

void sm_set_skey_next_skey ARG2(SM_SOFTKEY, 	skey,
				CHAR *,		next_key)
{
    int		i;

    assert(skey);

    for (i = 0; i < 8; i++)
	if (strcmp(skey->key_name[i], "sm_next_keys_c") == 0) {
	    SM_ASSIGN(skey->key_argument[i], next_key);
	    break;
	}
}

SM_SOFTKEY *sm_first_skey ARG1(SM_FILE, screenset)
{
    assert(screenset);

    return((SM_SOFTKEY *)firstArrayItemp(screenset->skey));
}

SM_SOFTKEY *sm_next_skey ARG1(SM_SOFTKEY *, skeyp)
{
    assert(*skeyp);

    return((SM_SOFTKEY *)nextArrayItemp((*skeyp)->parent->skey, (void **) skeyp));
}

/*
 *
 * Menu control and enquiry
 *
 */

SM_MENU sm_create_menu ARG1(SM_FILE, screenset)
{
    SM_MENU	menu;

    assert(screenset);

    menu = (SM_MENU) xmalloc((INDEX) sizeof(struct sm_menu));
    menu->menu_name = NULL;
    menu->type = 0;
    menu->items = NULL;
    menu->parent = screenset;

    screenset->menu = addToArray(screenset->menu, (void *) menu);
    return(menu);
}

/* Release memory */
static void _sm_destroy_menu_items ARG1(SM_MENU_ITEM, item)
{
    assert(item);

    if (item->text)
	free(item->text);
    if (item->command)
	free(item->command);
    free(item);
}

static void _sm_destroy_menu ARG1(SM_MENU, menu)
{
    assert(menu);

    if (menu->menu_name)
	free(menu->menu_name);

    if (menu->items) {
	removeAllItemsFromArray(menu->items, (f_ptr) _sm_destroy_menu_items);
	freeArray(menu->items);
    }

    free(menu);
}

/* Release memory */
SM_MENU *sm_destroy_menu ARG1(SM_MENU *, menu)
{
    assert(menu && *menu);

    _sm_destroy_menu(*menu);
    return((SM_MENU *) removeFromArrayp((*menu)->parent->menu, (void **) menu));
}

void sm_destroy_all_menus ARG1(SM_FILE, screenset)
{
    assert(screenset);

    if (screenset->menu)
	removeAllItemsFromArray(screenset->menu, (f_ptr) _sm_destroy_menu);
}

SM_MENU sm_menu_open ARG2(SM_FILE, screenset, CHAR *, menu_name) 
{
    SM_MENU	*menup;
    
    assert(screenset && menu_name && *menu_name);

    for (menup = sm_first_menu(screenset);
	 menup && *menup;
	 menup = sm_next_menu(menup))
	if (strcmp((*menup)->menu_name, menu_name) == 0)
	    return(*menup);
    return(NULL);
}

void sm_menu_init ARG1(SM_MENU, menu) 
{
}

void sm_paint_menu ARG1(SM_MENU, menu) 
{
    smio_paint_menu(menu);
    sm_current_menu = menu;
}

CHAR *sm_menu_name ARG1(SM_MENU, menu)
{
    assert(menu);
    return(menu->menu_name);
}

SM_MENU *sm_first_menu ARG1(SM_FILE, screenset)
{
    assert(screenset);
    return((SM_MENU *)firstArrayItemp(screenset->menu));
}

SM_MENU *sm_next_menu ARG1(SM_MENU *, menup)
{
    assert(menup && *menup);

    return((SM_MENU *)nextArrayItemp((*menup)->parent->menu, (void **) menup));
}

/*
 *
 * Background data
 *
 */

SM_BACKGROUND sm_create_bg ARG1(SM_SCREEN, screen)
{
    SM_BACKGROUND	bg = NULL;
    
    assert(screen);

    bg = (SM_BACKGROUND) xmalloc((INDEX) sizeof(struct sm_background));
    bg->text = NULL;
    bg->row = 0;
    bg->parent = screen;
    screen->background = addToArray(screen->background, bg);
    return(bg);
}

static void _sm_destroy_bg ARG1(SM_BACKGROUND, bg)
{
    assert(bg);

    if (bg->text)
	freeString(bg->text);
    free(bg);
}

SM_BACKGROUND *sm_destroy_bg ARG1(SM_BACKGROUND *, bgp)
{
    SM_SCREEN	screen = (*bgp)->parent;

    assert(bgp && *bgp);

    _sm_destroy_bg(*bgp);
    return((SM_BACKGROUND *) removeFromArrayp(screen->background, (void **)bgp));
}

void sm_destroy_all_bgs ARG1(SM_SCREEN, screen)
{
    assert(screen);

    if (screen->background)
	removeAllItemsFromArray(screen->background, (f_ptr) _sm_destroy_bg);
}

void sm_paint_bg ARG2(int, window, SM_BACKGROUND, bg)
{
    assert(bg);
    smio_goto(window, bg->row, 0);
    smio_puts(window, StringToStr(bg->text));
}

SM_BACKGROUND *sm_first_bg ARG1(SM_SCREEN, screen)
{
    assert(screen);

    return((SM_BACKGROUND *) firstArrayItemp(screen->background));
}

SM_BACKGROUND *sm_next_bg ARG1(SM_BACKGROUND *, bgp)
{
    assert(bgp && *bgp);

    return((SM_BACKGROUND *) nextArrayItemp((*bgp)->parent->background, 
					    (void *) bgp));
}

/* Find the background for this row */
SM_BACKGROUND sm_pick_bg ARG2(SM_SCREEN, screen, int, row)
{
    SM_BACKGROUND	*bgp;

    assert(screen);

    bgp = sm_first_bg(screen);
    while (bgp && *bgp) {
        if ((*bgp)->row == row)
            return(*bgp);
        bgp = sm_next_bg(bgp);
    }
    return(NULL);
}

/*
 *
 * Data field control and enquiry
 *
 */

SM_FIELD *sm_create_fld ARG1(SM_SCREEN, screen)
{
    SM_FIELD	field;

    assert(screen);

    field = (SM_FIELD) xmalloc((INDEX) sizeof(struct sm_field));
    field->field_name = NULL;
    field->row = 0;
    field->col = 0;
    field->attribute = 0;
    field->hidden = 0;
    field->protect = 0;
    field->ftype = 0;
    field->non_blank = 0;
    field->has_popup = 0;
    field->disallow_chdir = 0;
    field->disallow_insert = 0;
    field->len = 0;
    field->max_len = 0;
    field->changed = 0;
    field->c_value = NULL;
    field->c_default = NULL;
    field->master_screen_name = NULL;
    field->master_screen = NULL;
    field->master_fieldp = NULL;
    field->trigger_fn = NULL;
    field->next_field = NULL;
    field->pop_up = NULL;
    field->parent = screen;

    screen->field = addToArray(screen->field, field);
    return((SM_FIELD *) lastArrayItemp(screen->field));
}

static void _sm_destroy_fld ARG1(SM_FIELD, field)
{
    assert(field);
    
    if (field->field_name)
	free(field->field_name);
    if (field->c_value)
	free(field->c_value);
    if (field->c_default)
	free(field->c_default);
    if (field->master_screen_name)
	free(field->master_screen_name);
    if (field->next_field)
	free(field->next_field);
    if (field->pop_up)
	freeArray(field->pop_up);
    free(field);
}

SM_FIELD *sm_destroy_fld ARG1(SM_FIELD *, fieldp)
{
    SM_SCREEN	screen = (*fieldp)->parent;

    assert(fieldp && *fieldp);

    _sm_destroy_fld(*fieldp);
    return((SM_FIELD *)removeFromArrayp(screen->field, (void **) fieldp));
}

void sm_destroy_all_flds ARG1(SM_SCREEN, screen)
{
    assert(screen);

    if (screen->field)
	removeAllItemsFromArray(screen->field, (f_ptr) _sm_destroy_fld);
}

SM_FIELD *sm_fld_openp ARG2(SM_SCREEN, screen, CHAR *, field_name) 
{
    SM_FIELD	*fieldp;
    
    assert(screen && field_name);
    
    fieldp = sm_first_fld(screen);
    while (fieldp && *fieldp && strcmp((*fieldp)->field_name, field_name))
	fieldp = sm_next_fld(fieldp);
    return(fieldp);
}

SM_FIELD sm_fld_open ARG2(SM_SCREEN, screen, CHAR *, field_name) 
{
    SM_FIELD	*fieldp;

    assert(screen && field_name);

    fieldp = sm_fld_openp(screen, field_name);
    return(fieldp && *fieldp? *fieldp: NULL);
}

/* Set current value to default. Return default.
   For programmatically introduced popups, default (or mask for file/dir
   popups) is empty. */
CHAR *sm_fld_init ARG1(SM_FIELD, field)
{
    struct tm 	*ltime;
    long	clock;

    assert(field);

    if ((field->ftype == SM_FILE_FT) || (field->ftype == SM_DIR_FT)) {
	SM_ASSIGN(field->c_value, "");

    } else if (field->c_default && strcmp(field->c_default, "$date") == 0) {
	CHAR	buf[9];
	
	clock = time(NULL);
	ltime = localtime(&clock);
	sprintf(buf, "%4.4d%2.2d%2.2d", 
		ltime->tm_year+1900, 
		ltime->tm_mon + 1, 
		ltime->tm_mday);
	SM_ASSIGN(field->c_value, buf);
    } else if (field->has_popup) { 
	/* Bear in mind that a popup could still be created programmatically
	   by linking a list onto field->pop_up. Therefore, don't just
	   delete the list unless it was created here. */
	CHAR	*p, *d;
	
	SM_ASSIGN(field->c_value, "");

	if (*field->c_default) {
	    sm_delete_field_popups(field);
	    d = field->c_default;
	    do {
		p = strchr(d, ';');
		if (p)
		    *p = 0;
		sm_add_field_popup_item(field, d);
		if (p)
		    *p++ = ';';
		d = p;
	    } while (p);
	    SM_ASSIGN(field->c_value, 
		      StringToStr(*(STRING *)firstArrayItemp(field->pop_up)));
	}
    } else /* regular field */
	SM_FASSIGN(field->c_value, 
		   field->c_default, 
		   field->len < 0? abs(field->len): 0);
    
    return(field->c_value);
}

/* Is field to be non-blank/zero? */
int sm_non_blank ARG1(SM_FIELD, field)
{
    assert(field);

    return(field->non_blank);
}

/* Get current value */
CHAR *sm_fld_enq ARG1(SM_FIELD, field)
{
    assert(field);

    return(field->c_value);
}

CHAR *sm_fld_get ARG2(SM_SCREEN, screen, CHAR *, field_name) 
{
    assert(screen && field_name);

    return(sm_fld_enq(sm_fld_open(screen, field_name)));
}

/* Set current value */
void sm_fld_set ARG2(SM_FIELD, field, CHAR *, c_value)
{
    assert(field);
    
    SM_FASSIGN(field->c_value, 
	       c_value, 
	       field->len < 0? abs(field->len): 0);
    field->changed = TRUE;
}

/* Set hidden attribute */
void sm_fld_hidden ARG2(SM_FIELD, field, int, attribute)
{
    assert(field);

    field->attribute = attribute;
}

/* Set protected attribute */
void sm_fld_prot ARG2(SM_FIELD, field, int, attribute)
{
    assert(field);

    field->protect = attribute;
}

/* Set radix character */
void sm_fld_radix ARG3(SM_SCREEN, screen, SM_FIELD, field, CHAR, radix)
{
}

/* Set separator character */
void sm_set_separator ARG2(SM_FILE, screenset, CHAR, separator)
{
    assert(screenset);

    screenset->separator = separator;
}

/* Set enhancement */
void sm_fld_enh ARG3(SM_SCREEN, screen, SM_FIELD, field, ENH, enhancement)
{
}

/* Has a change been made since the last sm_enq_val/init/set? */
int sm_fld_changed ARG1(SM_FIELD, field)
{
    assert(field);

    return(field->changed);
}

SM_FIELD *sm_first_fld ARG1(SM_SCREEN, screen)
{
    assert(screen);

    return((SM_FIELD *) firstArrayItemp(screen->field));
}

SM_FIELD *sm_next_fld ARG1(SM_FIELD *, fieldp)
{
    SM_SCREEN screen = (*fieldp)->parent;

    assert(fieldp && *fieldp);

    return((SM_FIELD *) nextArrayItemp(screen->field, (void **) fieldp));
}

SM_FIELD *sm_prev_fld ARG1(SM_FIELD *, fieldp)
{
    SM_SCREEN screen = (*fieldp)->parent;

    assert(fieldp && *fieldp);

    return((SM_FIELD *) prevArrayItemp(screen->field, (void **) fieldp));
}

/*
 *
 * Data field manipulation
 *
 */

void sm_rename_fld ARG2(SM_FIELD, field, CHAR *, new_name)
{
    assert(field);

    SM_ASSIGN(field->field_name, new_name);
}

int sm_get_fld_len ARG1(SM_FIELD, field)
{
    assert(field);

    return(field? field->len: 0);
}

void sm_set_fld_len ARG2(SM_FIELD, field, int, new_length)
{
    assert(field);

    field->len = new_length;
    if (field->len < 0) {
        if (strlen(field->c_default) > abs(field->len))
            field->c_default[abs(field->len)] = 0;
	
        if (strlen(field->c_value) > abs(field->len))
            field->c_value[abs(field->len)] = 0;
    }
}

void sm_set_fld_type ARG2(SM_FIELD, field, int, field_type)
{
    assert(field);

    field->ftype = field_type;
}

/* Set default value */
void sm_fld_set_default ARG2(SM_FIELD, field, CHAR *, c_value)
{
    assert(field);

    SM_ASSIGN(field->c_default, c_value);
}

CHAR *sm_fld_get_default ARG1(SM_FIELD, field)
{
    assert(field);

    return(field->c_default);
}

void sm_set_fld_pos ARG3(SM_FIELD, field, int, new_row, int, new_col)
{
    assert(field);

    field->row = new_row;
    field->col = new_col;
}

void sm_set_fld_attr ARG4(SM_FIELD, field, int, attr, int, hidden, int, protect)
{
    assert(field);

    field->attribute = attr;
    field->hidden = hidden;
    field->protect = protect;
}

SM_SCREEN *sm_create_scr ARG1(SM_FILE, screenset)
{
    SM_SCREEN	screen;

    assert(screenset);

    screen = (SM_SCREEN) xmalloc ((INDEX) sizeof(struct sm_screen_s));
    screen->screen_name = NULL;
    screen->changed = 0;
    screen->filename = NULL;
    screen->data_field = NULL;
    screen->first_field = NULL;
    screen->softkey = NULL;
    screen->auto_adjust = 0;
    screen->skey = NULL;

    screen->scrolling = 0;
    screen->constant_start = NULL;
    screen->constant_end = NULL;
    screen->current_line = 0;
    screen->current_rec = 0;

    screen->temp_storage = 'M';
    screen->dirty_file = FALSE;
    screen->num_records = 0;
    screen->data = NULL;
    screen->index = 0;

    screen->lower = NULL;
    screen->upper = NULL;

    screen->background = NULL;
    screen->field = NULL;
    screen->parent = screenset;
    screen->timestamp_field = NULL;

    screen->background = newArray(25, 10);
    screen->field = newArray(10, 10);
    screen->sortkey = newArray(1, 1);

    screenset->scr = addToArray(screenset->scr, screen);

    return((SM_SCREEN *) lastArrayItemp(screenset->scr));
}

static void _sm_destroy_scr ARG1(SM_SCREEN, screen)
{
    assert(screen);

    if (screen->data) {
	f_ptr	f;

	f = (f_ptr) free;
	removeAllItemsFromArray(screen->data, f);
	freeArray(screen->data);
    }

    sm_destroy_all_flds(screen);
    freeArray(screen->field);

    sm_destroy_all_sortkeys(screen);
    freeArray(screen->sortkey);

    sm_destroy_all_bgs(screen);
    freeArray(screen->background);

    if (screen->filename)
	free(screen->filename);

    assert(screen->lower == NULL);
    assert(screen->upper == NULL);

    if (screen->data_field) {
	f_ptr	f;

	f = (f_ptr) free;
	removeAllItemsFromArray(screen->data_field, f);
	freeArray(screen->data_field);
    }

    if (screen->first_field)
	free(screen->first_field);

    if (screen->softkey)
	free(screen->softkey);

    if (screen->constant_start)
	free(screen->constant_start);

    if (screen->constant_end)
	free(screen->constant_end);

    if (screen->screen_name)
	free(screen->screen_name);
    
    free(screen);
}

SM_SCREEN *sm_destroy_scr ARG1(SM_SCREEN *, screenp)
{
    SM_SCREEN	screen = *screenp;

    assert(screenp);

    _sm_destroy_scr(*screenp);
    return((SM_SCREEN *) removeFromArrayp(screen->parent->scr, (void **) screenp));
}

void sm_destroy_all_scrs ARG1(SM_FILE, screenset)
{
    assert(screenset);

    if (screenset->scr)
	removeAllItemsFromArray(screenset->scr, (f_ptr) _sm_destroy_scr);
}

SM_SCREEN *sm_first_scr ARG1(SM_FILE, screenset)
{
    assert(screenset);

    return((SM_SCREEN *) firstArrayItemp(screenset->scr));
}

SM_SCREEN *sm_next_scr ARG1(SM_SCREEN *, screenp)
{
    assert(screenp && *screenp);

    return((SM_SCREEN *) nextArrayItemp((*screenp)->parent->scr, 
					(void **) screenp));
}

SM_SCREEN *sm_prev_scr ARG1(SM_SCREEN *, screenp)
{
    assert(screenp && *screenp);

    return((SM_SCREEN *) prevArrayItemp((*screenp)->parent->scr, 
					(void **) screenp));
}

SM_SCREEN sm_scr_open ARG2(SM_FILE, screenset, CHAR *, screen_name) 
{
    SM_SCREEN	*screenp;

    assert(screenset && screen_name);

    screenp = sm_first_scr(screenset);
    while (screenp && *screenp && strcmp((*screenp)->screen_name, screen_name))
	screenp = sm_next_scr(screenp);
    if (screenp && *screenp)
	sm_scr_init(*screenp, /* recursive = */ 1);
    return(screenp? *screenp: NULL);
}

void sm_scr_init ARG2(SM_SCREEN, screen, int, recursive) 
{
    SM_FIELD	*fieldp;
    
    assert(screen);

    for (fieldp = sm_first_fld(screen); 
	 fieldp && *fieldp; 
	 fieldp = sm_next_fld(fieldp)) {
	sm_fld_init(*fieldp);
	if (recursive && (*fieldp)->master_screen)
	    sm_scr_init((*fieldp)->master_screen, recursive);
    }
}

/* Has a change been made in any of the fields of this screen by the
   user since the last sm_enter_scr? */
int sm_scr_changed ARG1(SM_SCREEN, screen)
{
    assert(screen);

    return(screen->changed);
}

void sm_set_scr_unchanged ARG1(SM_SCREEN, screen)
{
    assert(screen);

    screen->changed = 0;
}

void sm_set_scr_changed ARG1(SM_SCREEN, screen)
{
    assert(screen);

    screen->changed = 1;
}

/* This assumes that the current record is loaded into the fields
   of the screen */
void sm_update_record_timestamp ARG1(SM_SCREEN, screen)
{
    CHAR	buf[20];
    struct tm 	*ltime;
    long	clock;

    assert(screen);

    if (screen->timestamp_field) {
	clock = time(NULL);
	ltime = localtime(&clock);
	sprintf(buf, "%4.4d%2.2d%2.2d%2.2d%2.2d%2.2d", 
		ltime->tm_year+1900, 
		ltime->tm_mon + 1, 
		ltime->tm_mday,
		ltime->tm_hour,
		ltime->tm_min,
		ltime->tm_sec);
	
	sm_fld_set(screen->timestamp_field, buf);
    }
}

/* Fill the screen fields from 'data'. 'data' is not destroyed. */
/* If the flag "static_part_only" is set on a scrolling screen, then only
   the key items will be copied to the screen and the screen is not
   initialised. This allows you to call this routine to fill up the main
   body of the fields and then call again to update the key items.
*/
int sm_data_to_screen ARG3(SM_SCREEN, screen, CHAR *, data, int, static_part_only)
{
    SM_FIELD	*fieldp, field;
    CHAR	*sep = NULL, *term;
    INDEX	data_desc = 0;
    int		in_static_part = 0;
    
    assert(screen && data);

    if (!static_part_only)
	sm_scr_init(screen, /* recursive = */ 0);

    if ((data == NULL) || (*data == 0))
	return(0);

    term = strchr(data, 0);
    fieldp = sm_first_fld(screen);

    while (data < term) {
	/* Decide which field to use */
	if (screen->data_field) {
	    CHAR	*p;

	    if ((p = (CHAR *)arrayItem(screen->data_field, data_desc)) == NULL)
		break;
	    
	    /* Use screen data descriptions to assign data to
	       appropriate fields */
	    field = sm_fld_open(screen, p);
	    if (field == NULL) {
		CHAR	*buf = alloca(100);

		sprintf(buf,"Error in screen \"%s\": field \"%s\" is missing!",
			screen->screen_name, 
			p);
		sm_error(buf);
		data_desc++;
		break;
	    }
	} else /* No screen data descriptions - fill fields sequentially */
	    field = *fieldp;

	if (!in_static_part && 
	    screen->constant_start &&
	    (strcmp(screen->constant_start, field->field_name) == 0))
	    in_static_part = 1;

	if (in_static_part && 
	    screen->constant_end &&
	    (strcmp(screen->constant_end, field->field_name) == 0))
	    in_static_part = 0;
	
	sep = sm_parse(data, 
		       (field && (field->len < 0))? abs(field->len): 0,
		       screen->parent->separator);
	
	if (!(screen->scrolling) ||
	    !static_part_only    ||
	    in_static_part) {
	    if (field) {
		field->c_value = 
		    sm_assign(field->c_value, 
			      data, 
			      (field->len<0)?abs(field->len):0);
		if ((field->len > 0) && 
		    field->max_len &&
		    (strlen(field->c_value) > field->max_len))
		    field->c_value[field->max_len] = 0;
	    }
	}
	
	
	/* Next piece of data */
	data = sm_nextfield(screen->parent->separator,
			    data, 
			    sep, 
			    (!field || (field->len >= 0)? 
			     0: abs(field->len)));
	
	if (sep && (sep < term))
	    *sep = screen->parent->separator;

	if (field && fieldp && *fieldp && (screen->data_field == NULL))
	    fieldp = sm_next_fld(fieldp);
	else
	    field = NULL;

	data_desc++;
    }
    
    if (sep && (sep < term))
	*sep = screen->parent->separator;
    return(0);
}

/* Copy the data from the screen to a buffer */
void sm_screen_to_data ARG2(CHAR *, data, SM_SCREEN, screen)
{
    SM_FIELD	*fieldp, field;
    int		data_desc = 0, length, delimit = 0;
    
    assert(screen && data);

    fieldp = sm_first_fld(screen);
    do {
	if (screen->data_field) {
	    CHAR	*p;

	    if ((p = (CHAR *) arrayItem(screen->data_field, data_desc++)) == NULL)
		break;
	    field = sm_fld_open(screen, p);
	} else {
	    /* Just copy it in screen order */
	    field = *fieldp;
	    if (fieldp && *fieldp)
		fieldp = sm_next_fld(fieldp);
	}

	if (field) {
            delimit = 0;
	    strcpy(data, field->c_value);
	    if (field->len < 0) {
		length = abs(field->len);
		while (strlen(data) < length)
		    strcat(data, " ");
		data += length;
		*data = 0;
	    } else {
		data = strchr(data, 0);
		*data++ = screen->parent->separator;
		*data = 0;
                delimit = 1;
	    }
	}
    } while (field);
    if (delimit)
	*--data = 0;
}

/* Decide which screen to use on the basis of the tag value */
/* Tag value is always in the same position in each record - it must always
   be preceded by the same fields. */
SM_SCREEN sm_choose_scr ARG2(SM_FILE, screenset, CHAR *, data)
{
    SM_SCREEN	*screenp;
    CHAR	*tag_value;

    assert(screenset && data);

    if ((screenset->tagname	== NULL) || 
	(data 			== NULL))
	return(NULL);

    screenp = sm_first_scr(screenset);
    if (screenp && *screenp) {
	sm_data_to_screen(*screenp, data, 0);
	tag_value = sm_fld_get(*screenp, screenset->tagname);
	if (tag_value)
	    return(sm_scr_open(screenset, tag_value));
    }
    return(NULL);
}

void sm_rename_scr ARG2(SM_SCREEN, screen, CHAR *, new_name)
{
    assert(screen && new_name);

    SM_ASSIGN(screen->screen_name, new_name);
}

SM_SORTKEY *sm_create_sortkey ARG1(SM_SCREEN, screen)
{
    SM_SORTKEY 	sortkey;

    assert(screen);

    sortkey = (SM_SORTKEY) xmalloc((INDEX) sizeof(struct sm_sortkey));

    sortkey->sortfield_name = NULL;
    sortkey->sortfield = NULL;
    sortkey->reverse = 0;
    sortkey->ignore_case = 1;
    sortkey->month = 0;
    sortkey->numeric = 1;
    sortkey->parent = screen;

    screen->sortkey = addToArray(screen->sortkey, sortkey);

    return((SM_SORTKEY *) lastArrayItemp(screen->sortkey));
}

static void _sm_destroy_sortkey ARG1(SM_SORTKEY, sortkey)
{
    assert(sortkey);

    if (sortkey->sortfield_name)
	free(sortkey->sortfield_name);
    free(sortkey);
}

SM_SORTKEY *sm_destroy_sortkey ARG1(SM_SORTKEY *, sortkeyp)
{
    SM_SCREEN	screen = (*sortkeyp)->parent;

    assert(sortkeyp && *sortkeyp);

    _sm_destroy_sortkey(*sortkeyp);
    return((SM_SORTKEY *)removeFromArrayp(screen->sortkey, (void **) sortkeyp));
}

void sm_destroy_all_sortkeys ARG1(SM_SCREEN, screen)
{
    assert(screen);

    if (screen->sortkey)
	removeAllItemsFromArray(screen->sortkey, (f_ptr) _sm_destroy_sortkey);
}

SM_SORTKEY *sm_first_sortkey ARG1(SM_SCREEN, screen)
{
    assert(screen);

    return((SM_SORTKEY *) firstArrayItemp(screen->sortkey));
}

SM_SORTKEY *sm_next_sortkey ARG1(SM_SORTKEY *, sortkeyp)
{
    SM_SCREEN	screen = (*sortkeyp)->parent;

    assert(sortkeyp && *sortkeyp);

    return((SM_SORTKEY *) nextArrayItemp(screen->sortkey, 
					 (void **) sortkeyp));
}

SM_SORTKEY sm_open_sortkey ARG2(SM_SCREEN, screen, CHAR *, name)
{
    SM_SORTKEY	*s;

    assert(screen && name);

    for (s = sm_first_sortkey(screen); s && *s; s = sm_next_sortkey(s))
	if (strcmp((*s)->sortfield_name, name) == 0)
	    return(*s);
    return(NULL);
}

void sm_mktemp ARG2(CHAR *, filename, CHAR *, pattern)
{
    CHAR	*pos;

    assert(filename && pattern);

    /* create a temporary file */
    pos = getenv("TMP");
    if (pos && *pos)
	strcpy(filename, pos);
    else
	strcpy(filename, "/tmp/");
    
#if BACKSLASH_AND_SLASH
    while ((pos = strchr(filename, '\\')) != NULL)
	*pos = '/';
#endif
    if (*(SM_LASTCHAR(filename)) != '/')
	strcat(filename, "/");
    strcat(filename, pattern);
    mkstemp(filename);
}

int sm_calc_sort_params ARG5(SM_SCREEN,	screen,
			     CHAR *,	fieldname,
			     int *,	field_num,
			     int *,	char_start,
			     int *,	char_end)
{
    SM_FIELD	*fieldp, f;
    CHAR	**p;
    int		finished = 0;

    assert(screen && fieldname && field_num && char_start && char_end);

    *field_num = 0;
    *char_start = 0;
    *char_end = 0;

    fieldp = sm_first_fld(screen);
    if (screen->data_field) 
        p = (CHAR **) firstArrayItemp(screen->data_field);
    else
        p = NULL;

    do {
	if (screen->data_field) {
	    if (p && *p)
		f = sm_fld_open(screen, *p);
	    else
		f = NULL;
	} else {
	    if (fieldp && *fieldp)
		f = *fieldp;
	    else
		f = NULL;
	}
	if (f && (strcmp(f->field_name, fieldname) == 0)) {
	    if (f->len < 0)
		*char_end = *char_start + abs(f->len);
	    else
		*char_end = 0;
	    finished = 1;
	} else if (f) {
	    if (f->len < 0)
		*char_start += abs(f->len);
	    else {
		(*field_num)++;
		*char_start = 0;
	    }
	}

	if (screen->data_field) 
	    p = (CHAR **) nextArrayItemp(screen->data_field, (void **) p);
	else
	    if (fieldp && *fieldp)
		fieldp = sm_next_fld(fieldp);
    } while (f && !finished);
    
    return(f? 0: -1);
}

/* For emacs:
 * Local Variables:
 * tab-width: 8
 * End:
 */
